Skip to content

Conversation

kornelski
Copy link
Contributor

@kornelski kornelski commented Sep 12, 2025

RFC for the issue rust-lang/rust#74182

Rendered

- The entire `#[stable(feature)]`/`#[unstable(feature)]` functionality could be stabilized for 3rd party crates
- API stability could be stored outside of the source code, e.g. in a file similar to `rustdoc`'s JSON
- It could be shortened to `#[since("version")]`
- It could be expanded to `#[stable(added = "version", changed = "version", rust_version = "msrv")]`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Does "msrv" really belong here? If the rustc version is too old the crate would simply refuse to be compiled, regardless if the you the tagged API or not. Besides you can't bump the MSRV without publishing the package, so it can be inferred from the changed crate version already.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see the argument that a person would maybe want to know "if I use this then I can't go back in rust version farther then X", but that's extremely rare. Usually people seem to have a particular (possibly locked) version of rust available, and then want to know what they can do while on that same version of rust.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the rustc version is too old the crate would simply refuse to be compiled

This is not true if the package detects the rustc version in use, which is already sometimes done through build scripts, and which #3857 proposes to make possible with a simple cfg.

@ehuss ehuss added T-lang Relevant to the language team, which will review and decide on the RFC. T-rustdoc Relevant to rustdoc team, which will review and decide on the RFC. labels Sep 12, 2025

Should the `version` allow a placeholder value like `UNRELEASED`?

Is it clear enough that the version is the crate's own version and not the minimum requierd Rust version?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you thinking that a person would see a 1.0-series crate with "stable since 1.34" or something, and then think that it means that the crate provides an item as long as the rust version is 1.34? Because that's... Not How It Works for anything else in rust crates. A particular user could think that, but as soon as they ask almost anyone else I'm sure they'd be corrected.

Copy link

@hanna-kruppe hanna-kruppe Sep 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, however, that there are real world examples of crates providing their own APIs based on the Rust version (e.g., camino and fs-err wrap std APIs and use Rust version detection to support new additions without constantly bumping MSRV).


Should crates reset the `version` when making semver-breaking changes to the item?

Should the `version` allow a placeholder value like `UNRELEASED`?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a rule like "you must put a string that can be a cargo crate version" is a simple enough rule, and however people want to get fancy with it is up to the ecosystem. For example, people could put the next minor version to be released, or put 0.0.0 as a placeholder, or whatever fits the project's tools and workflow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is indeed, but if people are going to use 999.999.999 or 0.0.0-unreleased etc. as placeholders, we could as well provide some placeholder out of the box.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love placeholder support as well as a teaching aid to use it (e.g. a clippy lint saying that the since is too new)

How does #[deprecated(since)] deal with any of this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://doc.rust-lang.org/reference/attributes/diagnostics.html#r-attributes.diagnostics.deprecated.syntax

"rustc does not currently interpret the string, but external tools like Clippy may check the validity of the value."

#[deprecated(since = "abcdef")] currently does not produce any lints or errors, but clippy denies with clippy::deprecated_semver. It does recognize the special string "TBD" though (rust-lang/rust-clippy#11850).


Versions on re-exported items are not relevant for the crate re-exporting them, because it matters when the re-export has been added.

# Rationale and alternatives
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There can be multiple ways something can be stable. Today, std also uses const_stable. I assume this should at least be acknowledged in case it can affect the design. Today they are separate attributes. Should we instead generalize stable to make it work for both? What impact may that have on this attribute?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's best to start with the simplest possible case and worry about the full situation later. I have some notes and design sketches for my thoughts if you'd like to discuss it on Zulip.


How to support items re-exported from other crates? Could `use` support overriding `#[stable(since)]`?

# Future possibilities
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cargo update could tell people to run some new command, like cargo report news clap@before clap@after and see what is newly available.

Even better if we can get some form of "behavior change" and "remove attributes" (along with the stable deprecated) along with build-flow analysis and tailor those parts of the report specifically to your use of the library (plus the new stuff)

@epage
Copy link
Contributor

epage commented Sep 14, 2025

Unsure what has been holding this up but would really love to have this!

@clarfonthey
Copy link

One thing worth considering here that distinguishes crate APIs from the standard library is that so far, the standard library has not introduced any truly breaking changes to things, but crates do all the time. It might be worth investigating how to properly distinguish that the stability attributes specifically indicate the stability of the current version of an API, and not just when the API was introduced.

- The entire `#[stable(feature)]`/`#[unstable(feature)]` functionality could be stabilized for 3rd party crates
- API stability could be stored outside of the source code, e.g. in a file similar to `rustdoc`'s JSON
- It could be shortened to `#[since("version")]`
- It could be expanded to `#[stable(added = "version", changed = "version", rust_version = "msrv")]`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like changed will need its own attribute, stacked on top of each other.

Should this be in future possibilities? How I've been visualizing all of this in my head, changed is another important element I would like us to have a serious discussion about at some point.

Huh, I guess changed could be added as an unstable attribute and applied to home as a way to start playing with it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed can't really be a future possibility, because at the rate these sorts of language things happen, people will have stable(since) for X many months already, and already think of the attribute as meaning one thing, and then eventually another attribute comes in and it changes how people should think about that.

So either it's in the initial design and all stabilized at once, or it can't really happen at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think changed must be stabilized together. It should be sufficient to have some plan for future extensions. The stable attribute can define how it's supposed to be used with APIs that changed, and attributes for providing extra information can be added later.

@epage
Copy link
Contributor

epage commented Sep 15, 2025

@clarfonthey

One thing worth considering here that distinguishes crate APIs from the standard library is that so far, the standard library has not introduced any truly breaking changes to things, but crates do all the time. It might be worth investigating how to properly distinguish that the stability attributes specifically indicate the stability of the current version of an API, and not just when the API was introduced.

I would prefer "first add" and we provide something else to track changes, like a #[changed] attribute, see also #3854 (comment)

@Lokathor
Copy link
Contributor

Personally, with the way I usually think about this, I'd like stable(since) to mean "stable in this form since", and the fact that something might have existed in some other way before the current iteration is just not as important. I don't need to read about something being stable since 1.6 and changed in 2.0 and then changed in 3.0, the 1.6 and 2.0 version info doesn't really help.

@jhpratt
Copy link
Member

jhpratt commented Sep 19, 2025

While I have a vision for reforming stability attributes in general and exposing them to all crates, I don't see any scenario where this is not a subset of what is ultimately decided.

Huge 👍 from me. My primary suggestion is to include a recommendation (but not a hard requirement) for a lint for future versions. Having since = "NEXT" or similar (as mentioned) would also be very useful for scenarios where you don't know if it's a patch, minor, or major version that'll be released next.

@jmillikin
Copy link

jmillikin commented Sep 20, 2025

If this attribute is intended for documentation and won't be enforced by the compiler, would it make sense to put it in the #[doc(...)] attribute?

#[doc(since = "2.25.0")]
pub fn add_trombone_emoji() {}


Should it be allowed on private items? (there's `--document-private-items`, but those items won't be accessible from outside of the crate).

Should it support specifying different kinds of stability, like `const_stable`?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be useful, because marking a function as const is forward-compatible.

If the #[doc(...)] syntax is used then an item might be annotated #[doc(since="1.1", const_since="1.23")].

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A flat doc(since) isn't specific enough. const_since is at least more specific. Neither allows an associate description which this doesn't need but future possibilities do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc is a rustdoc-specific attribute, and controls how the docs are generated.

I think availability and compatibility of APIs is more general than this. It's a semver/public API annotation that could be used beyond rustdoc.

Apart from that whether it's doc(since) or stable(since) is a bikeshed color.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bike shed, but I think it's the correct color for this shed. We're giving documentation. It's orderly and specific, but it's still documentation information about the item/method.

@kornelski
Copy link
Contributor Author

When considering why anyone needs this information, the primary use-case seems to be choosing the minimum required version of a crate (especially if the latest version of the crate requires a very new MSRV).

#[stable(since = "added")] with the first version in which an item has appeared seems more natural given the "stable since" terminology. However, it may be unsuitable for choosing the correct minimum required version if the oldest implementation isn't a compatible substitute for the latest one. What exactly is breaking for downgrades is hard to define, even harder than semver for upgrades.

There may be multiple kinds of features/behaviors stabilized separately. libstd has const_stable, but there may be library-specific functionality added. For example, wgpu features may be added to Vulcan backend much earlier than to WebGPU backend, and this distinction matters a lot to users who build for browsers.

So I'm wondering whether some changelog-like attribute would be better. If multiple #[changelog(version, note)] attributes were allowed, it would be easy to log multiple kinds of changes over time. min(version) would be "stable since", and max(version) would be a conservative minimum required crate version.

@kornelski
Copy link
Contributor Author

There's going to be a cfg() for Rust version: rust-lang/rust#64796 which together with doc(cfg) could be a way to display MSRV for each item in the documentation.

The since version in libstd is effectively both MSRV and the minimum libstd crate version, but in 3rd party crates these two could diverge, and theoretically both could be available at the same time.

@jmillikin
Copy link

#[stable(since = "added")] with the first version in which an item has appeared seems more natural given the "stable since" terminology. However, it may be unsuitable for choosing the correct minimum required version if the oldest implementation isn't a compatible substitute for the latest one. What exactly is breaking for downgrades is hard to define, even harder than semver for upgrades.

Let's say foolib-v1.1 adds a new function fn decode_u64(buf: &[u8]) -> u64. That symbol could be annotated with #[doc(since = "1.1")] to aid the reader in knowing which minimum version of foolib they need to depend on.

If foolib-v1.2 changes the behavior of decode_u64 to support a new encoded format then it doesn't make sense to change its availability annotation. Similarly if there was a bug that returned the wrong value for some inputs and v1.1.1 fixed that bug then it wouldn't make sense to bump the "available since" annotation.

There could be an annotation to mark the most recent version that modified a given function's observable behavior, but that seems like it would be difficult to keep in sync with the actual code -- imagining a deep call graph, does a fix in some inner core logic cause the entire API surface to get its "API mtime" bumped...?

So I'm wondering whether some changelog-like attribute would be better. If multiple #[changelog(version, note)] attributes were allowed, it would be easy to log multiple kinds of changes over time. min(version) would be "stable since", and max(version) would be a conservative minimum required crate version.

This seems like it would be a better fit for prose, such as a summary of changes in the API comment or a traditional CHANGELOG.txt. The rustdoc extended Markdown syntax for tables can be used to good effect for inline changelogs, because it's more flexible than a strict (version, note) tuple.

@kornelski
Copy link
Contributor Author

kornelski commented Sep 20, 2025

@jmillikin you say it doesn't make sense to change the availability annotation, but don't justify why. If decode_u64 started supporting a new format in v1.1, then for users needing the new format it would be important to know they can only use decode_u64 since v1.1, and earlier versions won't work. I'm trying to address that problem by suggesting to bump the since version, so that users get a version that will be compatible with the behavior specified in the documentation they're reading.

An attribute only specifying when an item has become available for the first time is simpler to define, but what would you use this information for? I'm trying to look at it from a perspective of use-cases to avoid merely adding facts because it's possible. This version needs to be useful for something.

Maybe the issue could be redefined in a documentation-centric way (justifying doc(since) attr): saying that this documentation as written applies to the item since the given version. So if the docs say "the decode function supports the new format", it needs to refer to some later library version that added the new format support, not the oldest version that added the function without new format support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC. T-rustdoc Relevant to rustdoc team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants